package com.tony.jdbc.dynamic.config;

import com.tony.jdbc.dynamic.datasource.DynamicDataSourceKeyHolder;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.autoconfigure.jdbc.JdbcProperties;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertyName;
import org.springframework.boot.context.properties.source.ConfigurationPropertyNameAliases;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.boot.jdbc.DatabaseDriver;
import org.springframework.boot.jdbc.XADataSourceWrapper;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;

import javax.sql.DataSource;
import javax.sql.XADataSource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author tony
 * XA数据源配置，参考XADataSourceAutoConfiguration
 */
@Configuration
@ConditionalOnProperty(prefix = "spring.jta",name = "enabled", havingValue = "true")
public class DynamicXADataSourceAndTemplateConfig implements BeanClassLoaderAware, InitializingBean {

    @Autowired
    JdbcProperties properties;
    @Autowired
    private DynamicDataSourceProperties dynamicDataSourceProperties;
    /**
     * jdbc操作对象 使用的是默认初始化的AtomikosDataSourceBeanBean类型'dataSource'
     */
    @Autowired
    private JdbcTemplate jdbcTemplate;
    private ClassLoader classLoader;
    @Autowired
    private ApplicationContext applicationContext;
    @Autowired
    private XADataSourceWrapper wrapper;
    //DataSourceProperties properties;
    @Autowired
    private ObjectProvider<XADataSource> xaDataSourceProvider;
    /**
     * 创建XA数据源
     *
     * @param wrapper          XA数据源包装器
     * @param properties       数据源属性
     * @param xaDataSource     XA数据源
     * @return XA数据源
     */
    public DataSource newDataSource(XADataSourceWrapper wrapper, DataSourceProperties properties,
                                 ObjectProvider<XADataSource> xaDataSource) throws Exception {
        return wrapper.wrapDataSource(xaDataSource.getIfAvailable(() -> createXaDataSource(properties)));
    }
    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    private XADataSource createXaDataSource(DataSourceProperties properties) {
        String className = properties.getXa().getDataSourceClassName();
        if (!StringUtils.hasLength(className)) {
            className = DatabaseDriver.fromJdbcUrl(properties.determineUrl()).getXaDataSourceClassName();
        }
        Assert.state(StringUtils.hasLength(className), "No XA DataSource class name specified");
        XADataSource dataSource = createXaDataSourceInstance(className);
        bindXaProperties(dataSource, properties);
        return dataSource;
    }

    private XADataSource createXaDataSourceInstance(String className) {
        try {
            Class<?> dataSourceClass = ClassUtils.forName(className, this.classLoader);
            Object instance = BeanUtils.instantiateClass(dataSourceClass);
            Assert.isInstanceOf(XADataSource.class, instance);
            return (XADataSource) instance;
        } catch (Exception ex) {
            throw new IllegalStateException("Unable to create XADataSource instance from '" + className + "'");
        }
    }

    private void bindXaProperties(XADataSource target, DataSourceProperties dataSourceProperties) {
        Binder binder = new Binder(getBinderSource(dataSourceProperties));
        binder.bind(ConfigurationPropertyName.EMPTY, Bindable.ofInstance(target));
    }

    private ConfigurationPropertySource getBinderSource(DataSourceProperties dataSourceProperties) {
        Map<Object, Object> properties = new HashMap<>(dataSourceProperties.getXa().getProperties());
        //xa数据源名称
        properties.put("uniqueResourceName",dataSourceProperties.getName());
        properties.computeIfAbsent("user", (key) -> dataSourceProperties.determineUsername());
        properties.computeIfAbsent("password", (key) -> dataSourceProperties.determinePassword());
        try {
            properties.computeIfAbsent("url", (key) -> dataSourceProperties.determineUrl());
        } catch (BeanCreationException ex) {
            // Continue as not all XA DataSource's require a URL
        }
        MapConfigurationPropertySource source = new MapConfigurationPropertySource(properties);
        ConfigurationPropertyNameAliases aliases = new ConfigurationPropertyNameAliases();
        aliases.addAliases("user", "username");
        return source.withAliases(aliases);
    }
    /**
     * 创建JdbcTemplate对象
     *
     * @param dataSource 数据源
     * @return JdbcTemplate对象
     */
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
        JdbcProperties.Template template = properties.getTemplate();
        jdbcTemplate.setFetchSize(template.getFetchSize());
        jdbcTemplate.setMaxRows(template.getMaxRows());
        if (template.getQueryTimeout() != null) {
            jdbcTemplate.setQueryTimeout((int) template.getQueryTimeout().getSeconds());
        }
        return jdbcTemplate;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        List<DataSourceProperties> allDatasourceList = dynamicDataSourceProperties.getAllDatasourceList(jdbcTemplate);
        for (DataSourceProperties dataSourceProperties : allDatasourceList) {
            DataSourceProperties.Xa xa = dataSourceProperties.getXa();
            DataSource dataSource = this.newDataSource(wrapper,dataSourceProperties, xaDataSourceProvider);
            ConfigurableListableBeanFactory autowireCapableBeanFactory = (ConfigurableListableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
            // 注册datasource、jdbcTemplate、namedJdbcTemplate到Spring容器
            String beanName = dataSourceProperties.getName();
            String jdbcTemplateBeanName = beanName + DynamicDataSourceKeyHolder.JDBC_TEMPLATE;
            String namedJdbcTemplateBeanName = beanName + DynamicDataSourceKeyHolder.NAMED_JDBC_TEMPLATE;
            if (dataSource instanceof BeanNameAware) {
                // 初始化数据源
                ((BeanNameAware) dataSource).setBeanName(beanName);
            }
            if (dataSource instanceof InitializingBean) {
                // 初始化数据源
                ((InitializingBean) dataSource).afterPropertiesSet();
            }
            JdbcTemplate jdbcTemplate = jdbcTemplate(dataSource);
            autowireCapableBeanFactory.registerSingleton(beanName, dataSource);
            autowireCapableBeanFactory.registerSingleton(jdbcTemplateBeanName, jdbcTemplate);
            autowireCapableBeanFactory.registerSingleton(namedJdbcTemplateBeanName, new NamedParameterJdbcTemplate(jdbcTemplate));
        }
    }
}
